package org.test4j.mock.faking;

import g_asm.org.objectweb.asm.Type;
import org.test4j.mock.Invocation;
import org.test4j.mock.MockUp;
import org.test4j.mock.faking.fluent.FluentMockUp;
import org.test4j.mock.faking.meta.FakeInvocation;
import org.test4j.mock.faking.meta.FakeMethod;
import org.test4j.mock.faking.modifier.BridgeFakeInvocation;
import org.test4j.mock.faking.modifier.BridgeTransformer;
import org.test4j.mock.faking.util.ClassLoad;
import org.test4j.mock.faking.util.ReflectUtility;
import org.test4j.mock.faking.util.TypeUtility;

import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.Objects;

import static org.test4j.mock.faking.modifier.BridgeFakeInvocation.Enter_Non_Mock_Block;
import static org.test4j.mock.faking.util.ReflectUtility.invoke;

/**
 * 执行调用{@link MockUp} 的mock操作
 *
 * @author darui.wu
 */
public class FakeInvoker {
    /**
     * 真实对象实例, not MockUp instance
     */
    private final Object target;
    /**
     * new MockUp()实例
     */
    private final FakeMethod fakeMethod;
    /**
     * 真实对象对应类名称描述 (L....;)
     */
    private final String realClassDesc;
    /**
     * 真实方法的方法名称
     */
    private final String methodName;
    /**
     * 真实方法的参数描述
     */
    private final String paramsDesc;

    /**
     * args of method, 第一个参数可能是Invocation
     */
    private final Object[] args;

    public FakeInvoker(Object target, FakeMethod fakeMethod, Class realClass, Method method, Object[] args) {
        this.target = target;
        this.fakeMethod = fakeMethod;
        this.realClassDesc = TypeUtility.classPath(realClass);
        this.methodName = method.getName();
        this.paramsDesc = Type.getMethodDescriptor(method);
        this.args = args;
    }

    public FakeInvoker(Object target, FakeMethod fakeMethod, String realClassDesc, String methodName,
                       String paramsDesc, Object[] args) {
        this.target = target;
        this.fakeMethod = fakeMethod;
        this.realClassDesc = realClassDesc;
        this.methodName = methodName;
        this.paramsDesc = paramsDesc;
        this.args = args;
    }

    /**
     * 执行具体的fake逻辑
     * <p>
     * called by {@link BridgeFakeInvocation#invoke(Object, Method, Object[])}
     *
     * @return
     */
    public Object callFakeMethod() {
        assert fakeMethod != null;
        if (this.fakeMethod.fake instanceof FluentMockUp) {
            return this.callFluentMockUpInvocation();
        } else if (this.fakeMethod.meta.hasInvocation) {
            return this.callMockWithInvocation();
        } else {
            return callMockWithoutInvocation(this.fakeMethod, args);
        }
    }

    private Object callFluentMockUpInvocation() {
        return ((FluentMockUp) this.fakeMethod.fake).invoke(this.newInvocation(), fakeMethod.meta.methodDesc, args);
    }

    /**
     * 调用没有 Invocation 参数的mock方法
     *
     * @return
     */
    public static Object callMockWithoutInvocation(FakeMethod fakeMethod, Object[] args) {
        Method method = fakeMethod.findMockMethod();
        return invoke(method, () -> method.invoke(fakeMethod.fake, args));
    }

    /**
     * 调用带 Invocation参数的mock方法
     *
     * @return
     */
    private Object callMockWithInvocation() {
        if (this.isCallSuperOverrideMethod()) {
            return Enter_Non_Mock_Block;
        } else {
            Invocation invocation = this.newInvocation();
            Object result = callMockWithInvocation(this.fakeMethod, invocation, args);
            if (invocation instanceof FakeInvocation && ((FakeInvocation) invocation).isProceedIntoConstructor()) {
                return Enter_Non_Mock_Block;
            } else {
                return result;
            }
        }
    }

    /**
     * 是否进入了重载方法调用
     * super.sameName(same para type)
     *
     * @return
     */
    private boolean isCallSuperOverrideMethod() {
        /** mock 静态方法 **/
        FakeInvocation invocation = this.fakeMethod.getProceedingInvocation();
        if (target == null || invocation == null) {
            return false;
        }
        Executable executable = invocation.getInvokedMember();
        if (target == invocation.getTarget() && executable instanceof Method) {
            String invokedClassDesc = TypeUtility.classPath(executable.getDeclaringClass());
            return !Objects.equals(invokedClassDesc, realClassDesc);
        } else {
            return false;
        }
    }


    public static Object callMockWithInvocation(FakeMethod fakeMethod, Invocation inv, Object[] args) {
        Object[] args2 = new Object[1 + args.length];
        args2[0] = inv;
        if (args.length > 0) {
            System.arraycopy(args, 0, args2, 1, args.length);
        }
        Method method = fakeMethod.findMockMethod();
        return invoke(method, () -> method.invoke(fakeMethod.fake, args2));
    }

    protected Invocation newInvocation() {
        Class fakedClass = ClassLoad.loadClass(realClassDesc);
        Executable invokedMember = ReflectUtility.findMethodByDesc(fakedClass, methodName, paramsDesc);
        return new FakeInvocation(target, args, fakeMethod, invokedMember);
    }

    public static String getHostClassName() {
        return BridgeTransformer.initBridgeField();
    }
}